/**
 *  Private methods section
 *  Consider all components as available directly from context
 */
privates = {
		
		/**
		 * Returns true when menu is displayed.
		 * 
		 * @returns boolean
		 */
		isMenuExpanded: function() {
			return !this.menu.is(':hidden');
		},
		
		/**
		 * Displays menu with highlighted actual selection.
		 */
		expandMenu: function() {
			this.highlightSelectedItem();
			/* Check if expanded menu can be fitted into window view port */
			var docViewTop = $(window).scrollTop();
			var docViewBottom = docViewTop + $(window).height();
			var comboTop = this.wrapper.offset().top;
			var menuHeight = parseInt(this.menu.css('height').replace('px', ''));
			var comboBottom = comboTop + this.wrapper.outerHeight() + menuHeight;
			var alternativeComboTop = comboTop - menuHeight;
			if (comboBottom > docViewBottom && alternativeComboTop > docViewTop) {
				this.menu.removeClass(this.options.classes.menu).addClass(this.options.classes.menuAbove);
			} else {
				this.menu.removeClass(this.options.classes.menuAbove).addClass(this.options.classes.menu);
			}
			/* IE 6 fix */
			this.wrapper.css('z-index', '1000');
			this.menu.show();
		},
		
		/**
		 * Hides menu.
		 * 
		 * @param selectHighlighted - if true, highlighted element just before closing the menu will be selected
		 */
		collapseMenu: function(selectHighlighted) {
			this.menu.hide();
			/* IE 6 fix */
			this.wrapper.css('z-index', '');
			if (selectHighlighted) {
				this.selectHighlightedItem();
			}
		},
		
		/**
		 * Toggles menu.
		 * 
		 * @param selectHighlighted - if true, highlighted element just before closing the menu will be selected
		 */
		toggleMenu: function(selectHighlighted) {
			if (this.isMenuExpanded()) {
				this.collapseMenu(selectHighlighted);
			} else {
				this.expandMenu();
			}
		},
		
		/**
		 * Get index of selected item.
		 * 
		 * @returns number - zero-indexed index
		 */
		getSelectedItemIndex: function() {
			return this.original.children('option:selected').index();
		},
		
		/**
		 * Selects given item.
		 * 
		 * @param item object
		 */
		selectItem: function(item) {
			this.selectIndex(this.menu.find('.' + this.options.classes.item).index(item));
		},
		
		/**
		 * Selects item which is currently highlighted.
		 */
		selectHighlightedItem: function() {
			this.selectIndex(this.getHighlightedItemIndex());
		},
		
		/**
		 * Selects previous item if possible.
		 */
		selectPrev: function() {
			/* Current index is calculated differently when menu is displayed or hidden */
			var currentIndex = this.isMenuExpanded() ? this.getHighlightedItemIndex() : this.getSelectedItemIndex();
			/* If there is some previous item */
			if (currentIndex > 0) {
				this.selectIndex(currentIndex - 1);
				this.highlightSelectedItem();
			}
		},
		
		/**
		 * Selects following item if possible.
		 */
		selectNext: function() {
			/* Current index is calculated differently when menu is displayed or hidden */
			var currentIndex = this.isMenuExpanded() ? this.getHighlightedItemIndex() : this.getSelectedItemIndex();
			/* If there is some following item */
			if (currentIndex < this.items.length - 1) {
				this.selectIndex(currentIndex + 1);
				this.highlightSelectedItem();
			}
		},
		
		/**
		 * Selects first item.
		 */
		selectFirst: function() {
			this.selectIndex(0);
			this.highlightSelectedItem();
		},
		
		/**
		 * Selects last item.
		 */
		selectLast: function() {
			this.selectIndex(this.items.length - 1);
			this.highlightSelectedItem();
		},
		
		/**
		 * Selects item on given index.
		 * 
		 * @param index number - zero-indexed position
		 */
		selectIndex: function(index) {
			/* If item with given index is already selected, don't perform anything */
			if (index === this.getSelectedItemIndex() ) {
				return;
			}
			/* Remove old selection */
			this.original.children('option').prop('selected', false);
			/* Select OPTION with given index */
			this.original.children('option:nth-child(' + (index + 1) + ')').prop('selected', true);
			/* Fire SELECT change event */
			this.original.change();
		},
		
		/**
		 * Returns index of currently highlighted item.
		 * 
		 * @returns number - zero-indexed index, -1 when there is no selected item
		 */
		getHighlightedItemIndex: function() {
			for (var i=0; i<this.items.length; i++) {
				if (this.items[i].hasClass(this.options.classes.itemHover)) {
					return i;
				}
			}
			return -1;
		},
		
		/**
		 * Clears highlights from all items.
		 */
		clearHighlight: function() {
			for (var i=0; i<this.items.length; i++) {
				this.items[i].removeClass(this.options.classes.itemHover);
			}
		},
		
		/**
		 * Highlights item on given index.
		 * 
		 * @param index number - zero-indexed position
		 */
		highlightItem: function(index) {
			this.clearHighlight();
			this.items[index].addClass(this.options.classes.itemHover);
		},
		
		/**
		 * Highlights currently selected item. This method works like a low-level repaint function.
		 */
		highlightSelectedItem: function() {
			this.highlightItem(this.getSelectedItemIndex());
		},
		
		/**
		 * Repaints caption component.
		 */
		repaintCaption: function() {
			/* If caption text should be displayed */
			if (this.options.displayCaption) {
				/* Set caption text */
				this.caption.text(this.original.children(':selected').text());
			} else {
				/* Clear caption content */
				this.caption.empty();
			}
			/* If icons are forbidden or there are defined any, don't proceed further */
			if (!this.options.displayIcons || !this.useIcons) {
				return;
			}
			/* Gets icon path from OPTION data-* attribute */
			var iconPath = this.original.children(':selected').attr('data-' + this.options.data.icon);
			/* If path is defined, add icon element */
			if (iconPath) {
				this.options.createElement('itemicon').prependTo(this.caption).css('backgroundImage', 'url("' + iconPath + '")');
			}
		},
		
		/**
		 * Adds icons to item elements if necessary.
		 */
		showIcons: function() {
			/* If icons shoudln't be displayed or items already contains icons, don't proceed further */
			if (!this.options.displayIcons || !this.useIcons || this.menu.find('.' + this.options.classes.item).children(this.options.elements.itemicon).length) {
				return;
			}
			for (var i=0; i<this.items.length; i++) {
				/* Create icon element */
				var icon = this.options.createElement('itemicon').prependTo(this.items[i]),
				/* Gets icon path from OPTION data-* attribute */
				iconPath = this.original.children('option:nth-child(' + (i + 1) + ')').attr('data-' + this.options.data.icon);
				/* If path is defined, set the background of icon element */
				if (iconPath) {
					icon.css('backgroundImage', 'url("' + iconPath + '")');
				}
			}
		},
		
		/**
		 * Enables component.
		 */
		enable: function() {
			/* Remove read-only flag from original select */
			this.original.prop('readonly', false);
			
			/* Remove disabled class and bind event handlers */
			this.original.bind('change.' + namespace, handlers.originalChange);
			this.wrapper.removeClass(this.options.classes.wrapperDisabled)
			            .bind('mouseover.' + namespace, handlers.wrapperMouseOver)
			            .bind('mouseout.' + namespace, handlers.wrapperMouseOut)
			            .bind('mousedown.' + namespace, handlers.wrapperMouseDown)
			            .bind('mouseup.' + namespace, handlers.wrapperMouseUp);
			this.caption.bind('mousedown.' + namespace, handlers.captionMouseDown)
			            .bind('blur.' + namespace, handlers.captionBlur)
			            .bind('keydown.' + namespace, handlers.captionKeyDown);
			this.arrow.bind('mousedown.' + namespace, handlers.arrowMouseDown)
			          .bind('mouseup.' + namespace, handlers.arrowMouseUp);
			
			/* Bind event handlers to combo items */
			$.each(this.items, function(index, item) {
				item.bind('mouseover.' + namespace, handlers.itemMouseOver)
				    .bind('click.' + namespace, handlers.itemClick);
			});
			
			/* If caption element is A - HTML link... */
			if (this.caption.is('a')) {
				/* Add empty href attribute, so it can be focused */
				this.caption.attr('href', '')
				/* Prevent page reload on click */
				            .bind('click.' + namespace, handlers.doNothing);
			}
			
			/* If arrow element is A - HTML link... */
			if (this.arrow.is('a')) {
				/* Add empty href attribute, so it can be focused */
				this.arrow.attr('href', '')
				/* Prevent page reload on click */
				          .bind('click.' + namespace, handlers.doNothing);
			}
		},
		
		/**
		 * Disables component.
		 */
		disable: function(isDestroy) {
			
			if (!isDestroy) {
				/* Add read-only flag to original select */
				this.original.prop('readonly', true);
			}
			
			/* Add disabled class and unbound event handlers */
			this.original.unbind('change.' + namespace);
			this.wrapper.addClass(this.options.classes.wrapperDisabled)
			            .unbind('mouseover.' + namespace)
			            .unbind('mouseout.' + namespace)
			            .unbind('mousedown.' + namespace)
			            .unbind('mouseup.' + namespace);
			this.caption.unbind('mousedown.' + namespace)
			            .unbind('blur.' + namespace)
			            .unbind('keydown.' + namespace);
			this.arrow.unbind('mousedown.' + namespace)
			          .unbind('mouseup.' + namespace);
			
			/* Unbind event handlers from combo items */
			$.each(this.items, function(index, item) {
				item.unbind('mouseover.' + namespace)
				    .unbind('click.' + namespace);
			});
			
			/* If caption element is A - HTML link... */
			if (this.caption.is('a')) {
				/* Remove href attribute so it can't be focused */
				this.caption.removeAttr('href');
			}
			
			/* If arrow element is A - HTML link... */
			if (this.arrow.is('a')) {
				/* Remove href attribute so it can't be focused */
				this.arrow.removeAttr('href');
			}
		},
		
		/**
		 * Sets width of imgcombo. If width was defined by CSS on original SELECT then
		 * new component will extend this value, otherwise it is calculated to be smallest
		 * possible with given item values.
		 */
		setWidth: function() {
			/* If style with width is defined on original SELECT, use it and don't proceed further */
			if (this.original[0].style.width) {
				this.wrapper.css('width', this.original[0].style.width);
				return;
			}
			/* Look for width definition in external style sheets */
			//TODO following loop is not respecting cascading character of CSS rules - last one is used
			var sheets = document.styleSheets, result;
			for(var i=0; i<sheets.length; i++) {
				try {
					var rules = sheets[i].rules || sheets[i].cssRules;
					for (var j=0; j<rules.length; j++) {
						try {
							if (rules[j].style.width && this.original.is(rules[j].selectorText)) {
								result = rules[j].style.width;
							}
						} catch(e) {
							/* jQuery is() function have problems in IE with rules containing ":" character */
						}
					}
				} catch(e) {
					/* Some browsers throws security exception when accessing style sheet from different domain */
				}
			}
			if (result) {
				this.wrapper.css('width', result);
				return;
			}
			
			var max = 0, handleHidden = !this.isMenuExpanded();
			/* If menu is hidden, change CSS to be measurable by JS */
			if (handleHidden) {
				this.menu.css({visibility: 'hidden', display: 'block'});
			}
			/* Find item with maximal width */
			for (var i=0; i<this.items.length; i++) {
				var width = this.items[i].children(this.options.elements.itemtext + ':first').outerWidth(true);
				/* Add width of icon element if it is present */
				if (this.useIcons && this.items[i].children(this.options.elements.itemicon + ':first').length) {
					width += this.items[i].children(this.options.elements.itemicon + ':first').outerWidth(true);
				}
				max = Math.max(max, width);
			}
			/* Restore CSS when needed */
			if (handleHidden) {
				this.menu.css({visibility: '', display: 'none'});
			}
			/* Add also width of arrow component */
			this.wrapper.css('width', max + this.arrow.width() + new Number(this.caption.css('padding-right').replace('px', '')) + 'px');
		},
		
		/**
		 * Sets height of menu component if it is needed.
		 */
		setHeight: function() {
			/* If current item count is grater than max item count to be displayed */
			if (this.items.length && this.items.length > this.options.itemCount) {
				var handleHidden = !this.isMenuExpanded();
				/* If menu is hidden, change CSS to be measurable by JS */
				if (handleHidden) {
					this.menu.css({visibility: 'hidden', display: 'block'});
				}
				/* Calculate needed height and set it */
				this.menu.css('height', (this.items[0].outerHeight(true) * this.options.itemCount) + 'px');
				if ($.fn.jScrollPane) {
					this.menu.jScrollPane();
					var track = this.menu.find('.jspVerticalBar .jspTrack').hover(function(){
						$(this).prev().addClass('jspArrowUpHover');
						$(this).next().addClass('jspArrowDownHover');
					}, function() {
						$(this).prev().removeClass('jspArrowUpHover');
						$(this).next().removeClass('jspArrowDownHover');
					});
					track.css('height', (track.height() + 1) + 'px');
					track.prev().hover(function() {
						$(this).next().next().addClass('jspArrowDownHover');
					}, function() {
						$(this).next().next().removeClass('jspArrowDownHover');
					});
					track.next().hover(function() {
						$(this).prev().prev().addClass('jspArrowUpHover');
					}, function() {
						$(this).prev().prev().removeClass('jspArrowUpHover');
					});
					if (!jQuery.browser.mozilla) {
						var drag = this.menu.find('.jspVerticalBar .jspDrag');
						drag.css('height', (drag.height() + 1) + 'px');
					}
				}
				/* Restore CSS when needed */
				if (handleHidden) {
					this.menu.css({visibility: '', display: 'none'});
				}
			}
		},
		
		/**
		 * Layout components to start state.
		 */
		layout: function() {
			//TODO re-think this section...
			var wasInitiallyDisabled = this.original.is(':disabled') || this.original.is('[readonly]');
			this.enable();
			if (wasInitiallyDisabled) {
				this.disable();
			}
			this.original.hide();
			this.repaintCaption();
			this.showIcons();
			this.setWidth();
			this.setHeight();
			this.collapseMenu();
		}
		
};

